import java.util.TreeMap;
/**
- * {@code UnknownFieldSet} is used to keep track of fields which were seen when
+ * {@code UnknownFieldSet} keeps track of fields which were seen when
* parsing a protocol message but whose field numbers or types are unrecognized.
* This most frequently occurs when new fields are added to a message type
* and then messages containing those fields are read by old software that was
* compiled before the new types were added.
*
* <p>Every {@link Message} contains an {@code UnknownFieldSet} (and every
- * {@link Message.Builder} contains an {@link Builder}).
+ * {@link Message.Builder} contains a {@link Builder}).
*
* <p>Most users will never need to use this class.
*
*/
public final class UnknownFieldSet implements MessageLite {
- private UnknownFieldSet() {
- fields = null;
+ private final TreeMap<Integer, Field> fields;
+
+ /**
+ * Construct an {@code UnknownFieldSet} around the given map.
+ */
+ UnknownFieldSet(TreeMap<Integer, Field> fields) {
+ this.fields = fields;
}
/** Create a new {@link Builder}. */
return defaultInstance;
}
private static final UnknownFieldSet defaultInstance =
- new UnknownFieldSet(Collections.<Integer, Field>emptyMap(),
- Collections.<Integer, Field>emptyMap());
-
- /**
- * Construct an {@code UnknownFieldSet} around the given map. The map is
- * expected to be immutable.
- */
- UnknownFieldSet(final Map<Integer, Field> fields,
- final Map<Integer, Field> fieldsDescending) {
- this.fields = fields;
- }
- private final Map<Integer, Field> fields;
+ new UnknownFieldSet(new TreeMap<Integer, Field>());
@Override
@Override
public int hashCode() {
+ if (fields.isEmpty()) { // avoid allocation of iterator.
+ // This optimization may not be helpful but it is needed for the allocation tests to pass.
+ return 0;
+ }
return fields.hashCode();
}
/** Get a map of fields in the set by number. */
public Map<Integer, Field> asMap() {
- return fields;
+ return (Map<Integer, Field>) fields.clone();
}
/** Check if the given field number is present in the set. */
@Override
public void writeDelimitedTo(OutputStream output) throws IOException {
final CodedOutputStream codedOutput = CodedOutputStream.newInstance(output);
- codedOutput.writeRawVarint32(getSerializedSize());
+ codedOutput.writeUInt32NoTag(getSerializedSize());
writeTo(codedOutput);
codedOutput.flush();
}
@Override
public int getSerializedSize() {
int result = 0;
- for (final Map.Entry<Integer, Field> entry : fields.entrySet()) {
- result += entry.getValue().getSerializedSize(entry.getKey());
+ if (!fields.isEmpty()) {
+ for (Map.Entry<Integer, Field> entry : fields.entrySet()) {
+ result += entry.getValue().getSerializedSize(entry.getKey());
+ }
}
return result;
}
// This constructor should never be called directly (except from 'create').
private Builder() {}
- private Map<Integer, Field> fields;
-
- // Optimization: We keep around a builder for the last field that was
- // modified so that we can efficiently add to it multiple times in a
- // row (important when parsing an unknown repeated field).
- private int lastFieldNumber;
- private Field.Builder lastField;
+ private TreeMap<Integer, Field.Builder> fieldBuilders =
+ new TreeMap<Integer, Field.Builder>();
private static Builder create() {
- Builder builder = new Builder();
- builder.reinitialize();
- return builder;
+ return new Builder();
}
/**
* values that already exist.
*/
private Field.Builder getFieldBuilder(final int number) {
- if (lastField != null) {
- if (number == lastFieldNumber) {
- return lastField;
- }
- // Note: addField() will reset lastField and lastFieldNumber.
- addField(lastFieldNumber, lastField.build());
- }
if (number == 0) {
return null;
} else {
- final Field existing = fields.get(number);
- lastFieldNumber = number;
- lastField = Field.newBuilder();
- if (existing != null) {
- lastField.mergeFrom(existing);
+ Field.Builder builder = fieldBuilders.get(number);
+ if (builder == null) {
+ builder = Field.newBuilder();
+ fieldBuilders.put(number, builder);
}
- return lastField;
+ return builder;
}
}
/**
* Build the {@link UnknownFieldSet} and return it.
- *
- * <p>Once {@code build()} has been called, the {@code Builder} will no
- * longer be usable. Calling any method after {@code build()} will result
- * in undefined behavior and can cause a {@code NullPointerException} to be
- * thrown.
*/
@Override
public UnknownFieldSet build() {
- getFieldBuilder(0); // Force lastField to be built.
final UnknownFieldSet result;
- if (fields.isEmpty()) {
+ if (fieldBuilders.isEmpty()) {
result = getDefaultInstance();
} else {
- Map<Integer, Field> descendingFields = null;
- result = new UnknownFieldSet(Collections.unmodifiableMap(fields), descendingFields);
+ TreeMap<Integer, Field> fields = new TreeMap<Integer, Field>();
+ for (Map.Entry<Integer, Field.Builder> entry : fieldBuilders.entrySet()) {
+ fields.put(entry.getKey(), entry.getValue().build());
+ }
+ result = new UnknownFieldSet(fields);
}
- fields = null;
return result;
}
@Override
public Builder clone() {
- getFieldBuilder(0); // Force lastField to be built.
- Map<Integer, Field> descendingFields = null;
- return UnknownFieldSet.newBuilder().mergeFrom(
- new UnknownFieldSet(fields, descendingFields));
+ Builder clone = UnknownFieldSet.newBuilder();
+ for (Map.Entry<Integer, Field.Builder> entry : fieldBuilders.entrySet()) {
+ Integer key = entry.getKey();
+ Field.Builder value = entry.getValue();
+ clone.fieldBuilders.put(key, value.clone());
+ }
+ return clone;
}
@Override
return UnknownFieldSet.getDefaultInstance();
}
- private void reinitialize() {
- fields = Collections.emptyMap();
- lastFieldNumber = 0;
- lastField = null;
- }
-
/** Reset the builder to an empty set. */
@Override
public Builder clear() {
- reinitialize();
+ fieldBuilders = new TreeMap<Integer, Field.Builder>();
return this;
}
- /** Clear fields from the set with a given field number. */
+ /**
+ * Clear fields from the set with a given field number.
+ *
+ * @throws IllegalArgumentException if number is not positive
+ */
public Builder clearField(final int number) {
- if (number == 0) {
- throw new IllegalArgumentException("Zero is not a valid field number.");
+ if (number <= 0) {
+ throw new IllegalArgumentException(number + " is not a valid field number.");
}
- if (lastField != null && lastFieldNumber == number) {
- // Discard this.
- lastField = null;
- lastFieldNumber = 0;
- }
- if (fields.containsKey(number)) {
- fields.remove(number);
+ if (fieldBuilders.containsKey(number)) {
+ fieldBuilders.remove(number);
}
return this;
}
/**
* Add a field to the {@code UnknownFieldSet}. If a field with the same
* number already exists, the two are merged.
+ *
+ * @throws IllegalArgumentException if number is not positive
*/
public Builder mergeField(final int number, final Field field) {
- if (number == 0) {
- throw new IllegalArgumentException("Zero is not a valid field number.");
+ if (number <= 0) {
+ throw new IllegalArgumentException(number + " is not a valid field number.");
}
if (hasField(number)) {
getFieldBuilder(number).mergeFrom(field);
* Convenience method for merging a new field containing a single varint
* value. This is used in particular when an unknown enum value is
* encountered.
+ *
+ * @throws IllegalArgumentException if number is not positive
*/
public Builder mergeVarintField(final int number, final int value) {
- if (number == 0) {
- throw new IllegalArgumentException("Zero is not a valid field number.");
+ if (number <= 0) {
+ throw new IllegalArgumentException(number + " is not a valid field number.");
}
getFieldBuilder(number).addVarint(value);
return this;
* Convenience method for merging a length-delimited field.
*
* <p>For use by generated code only.
+ *
+ * @throws IllegalArgumentException if number is not positive
*/
public Builder mergeLengthDelimitedField(
final int number, final ByteString value) {
- if (number == 0) {
- throw new IllegalArgumentException("Zero is not a valid field number.");
+ if (number <= 0) {
+ throw new IllegalArgumentException(number + " is not a valid field number.");
}
getFieldBuilder(number).addLengthDelimited(value);
return this;
/** Check if the given field number is present in the set. */
public boolean hasField(final int number) {
- if (number == 0) {
- throw new IllegalArgumentException("Zero is not a valid field number.");
- }
- return number == lastFieldNumber || fields.containsKey(number);
+ return fieldBuilders.containsKey(number);
}
/**
* Add a field to the {@code UnknownFieldSet}. If a field with the same
* number already exists, it is removed.
+ *
+ * @throws IllegalArgumentException if number is not positive
*/
public Builder addField(final int number, final Field field) {
- if (number == 0) {
- throw new IllegalArgumentException("Zero is not a valid field number.");
- }
- if (lastField != null && lastFieldNumber == number) {
- // Discard this.
- lastField = null;
- lastFieldNumber = 0;
- }
- if (fields.isEmpty()) {
- fields = new TreeMap<Integer,Field>();
+ if (number <= 0) {
+ throw new IllegalArgumentException(number + " is not a valid field number.");
}
- fields.put(number, field);
+ fieldBuilders.put(number, Field.newBuilder(field));
return this;
}
* fields are added, the changes may or may not be reflected in this map.
*/
public Map<Integer, Field> asMap() {
- getFieldBuilder(0); // Force lastField to be built.
+ TreeMap<Integer, Field> fields = new TreeMap<Integer, Field>();
+ for (Map.Entry<Integer, Field.Builder> entry : fieldBuilders.entrySet()) {
+ fields.put(entry.getKey(), entry.getValue().build());
+ }
return Collections.unmodifiableMap(fields);
}
* <p>Use {@link Field#newBuilder()} to construct a {@code Builder}.
*/
public static final class Builder {
- // This constructor should never be called directly (except from 'create').
- private Builder() {}
+ // This constructor should only be called directly from 'create' and 'clone'.
+ private Builder() {
+ result = new Field();
+ }
private static Builder create() {
Builder builder = new Builder();
- builder.result = new Field();
return builder;
}
private Field result;
+ @Override
+ public Builder clone() {
+ Field copy = new Field();
+ if (result.varint == null) {
+ copy.varint = null;
+ } else {
+ copy.varint = new ArrayList<Long>(result.varint);
+ }
+ if (result.fixed32 == null) {
+ copy.fixed32 = null;
+ } else {
+ copy.fixed32 = new ArrayList<Integer>(result.fixed32);
+ }
+ if (result.fixed64 == null) {
+ copy.fixed64 = null;
+ } else {
+ copy.fixed64 = new ArrayList<Long>(result.fixed64);
+ }
+ if (result.lengthDelimited == null) {
+ copy.lengthDelimited = null;
+ } else {
+ copy.lengthDelimited = new ArrayList<ByteString>(result.lengthDelimited);
+ }
+ if (result.group == null) {
+ copy.group = null;
+ } else {
+ copy.group = new ArrayList<UnknownFieldSet>(result.group);
+ }
+
+ Builder clone = new Builder();
+ clone.result = copy;
+ return clone;
+ }
+
/**
- * Build the field. After {@code build()} has been called, the
- * {@code Builder} is no longer usable. Calling any other method will
- * result in undefined behavior and can cause a
- * {@code NullPointerException} to be thrown.
+ * Build the field.
*/
public Field build() {
+ Field built = new Field();
if (result.varint == null) {
- result.varint = Collections.emptyList();
+ built.varint = Collections.emptyList();
} else {
- result.varint = Collections.unmodifiableList(result.varint);
+ built.varint = Collections.unmodifiableList(new ArrayList<Long>(result.varint));
}
if (result.fixed32 == null) {
- result.fixed32 = Collections.emptyList();
+ built.fixed32 = Collections.emptyList();
} else {
- result.fixed32 = Collections.unmodifiableList(result.fixed32);
+ built.fixed32 = Collections.unmodifiableList(new ArrayList<Integer>(result.fixed32));
}
if (result.fixed64 == null) {
- result.fixed64 = Collections.emptyList();
+ built.fixed64 = Collections.emptyList();
} else {
- result.fixed64 = Collections.unmodifiableList(result.fixed64);
+ built.fixed64 = Collections.unmodifiableList(new ArrayList<Long>(result.fixed64));
}
if (result.lengthDelimited == null) {
- result.lengthDelimited = Collections.emptyList();
+ built.lengthDelimited = Collections.emptyList();
} else {
- result.lengthDelimited =
- Collections.unmodifiableList(result.lengthDelimited);
+ built.lengthDelimited = Collections.unmodifiableList(
+ new ArrayList<ByteString>(result.lengthDelimited));
}
if (result.group == null) {
- result.group = Collections.emptyList();
+ built.group = Collections.emptyList();
} else {
- result.group = Collections.unmodifiableList(result.group);
+ built.group = Collections.unmodifiableList(new ArrayList<UnknownFieldSet>(result.group));
}
- final Field returnMe = result;
- result = null;
- return returnMe;
+ return built;
}
/** Discard the field's contents. */